/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package entities;

import entities.exceptions.BlockedTileException;
import entities.exceptions.CheckpointNotAtRouteException;
import entities.exceptions.CrossingOccupiedException;
import entities.exceptions.EmptyRouteException;
import entities.exceptions.RouteIndexOutOfRangeException;
import graphics.WindowManager;
import java.awt.Image;
import java.awt.Point;
import java.util.LinkedList;
import willispace.Config;

/**
 *
 * @author mateusz
 */
public abstract class Robot extends Entity implements Runnable {
    
    /**
     * thread of this robot
     */
    protected Thread t;
    private boolean isRunning;
    // movement
    private volatile int dx;
    private int dxCopy;
    private volatile int dy;
    private int dyCopy;
    // associated tiles
    private volatile Tile incomingTile;
    private volatile Tile outcomingTile;

    /**
     * list of targets
     */
    protected LinkedList<Target> targets;
    /**
     * if route is cyclic
     */
    protected boolean cycleRoute;
    /**
     * current route
     */
    protected Route route;
    /**
     * initializer
     */
    protected boolean init;

    /**
     * Creates robot
     * @param tile          associacted tile
     * @param defaultImage  image
     * @param myName        name
     */
    public Robot(Tile tile, Image defaultImage, String myName) {
        super(tile.getXY(), defaultImage, WindowManager.fullScreenWindow.topPanel, myName);

        // movement
        dx = 0;
        dy = 0;

        // targets
        targets = new LinkedList<Target>();

        // associated tiles
        incomingTile = outcomingTile = tile;
        cycleRoute = false; // by default route is not cyclic
        route = new Route();

        // thread stuff
        isRunning = true;
        init = false;
        t = new Thread(this);
        t.setName(myName);
        //t.start();
    }

    /**
     * Action when target reached
     * @param target    target reached
     */
    protected abstract void action(Target target);

    @Override
    protected void updateProperties() {
        super.updateProperties();
    }

    @Override
    public void updateLocation() {
        if (isRunning()) {
            super.updateLocation();
        }
    }

    /**
     * Updates robot status, position. 
     * Check for crossings, targets.
     * Moves robot.
     */
    protected synchronized void update() {
        boolean doMove = true;
        // if robot coverages tile
        if (checkTileCovering()) {
            // try go to next tile
            while (isRunning()) {
                try {
                    // try take next crossing
                    incomingTile = occupyNextCheckpoint();
                    
                    directionsUpdate();

                    // going out from current tile
                    outcomingTile.goingOut(this);

                    break;
                }
                catch (RouteIndexOutOfRangeException ex) {
                    doMove = false;
                    goSleep();
                } catch (CrossingOccupiedException ex) {
                    //goSleep();
                    doMove = false;
                    break;
                }
            }
        }
        if (doMove) {
            // do move
            this.x += dx;
            this.y += dy;
            
            if (checkTileCovering()) {
                // set directions to 0
                resetDirections(); 
                // unoccupy previous crossing
                unoccupyCheckpoint(outcomingTile);

                try {
                    route.deleteCheckpoint(outcomingTile, !cycleRoute);
                } catch (CheckpointNotAtRouteException ex) {
                }
                
                outcomingTile = incomingTile; // incoming tile became incoming

                // action if target reached
                if (incomingTile == getNextTarget().tile) {
                    action(removeNextTarget());
                }
            }
            updateProperties();
        }

        
        updateLocation();
    }

    /**
     * initialize some values
     */
    protected void init() {
        init = false;
    }

    @Override
    public void run() {
        if (init) {
            init();
        }
        while (isRunning()) {
            try {
                if (!isPaused) {
                    update();
                }
                Thread.sleep(Config.EntitySleepTime);
            } catch (InterruptedException ex) {
            }

        }
    }

    /**
     * Kill this thread by set isRunning to false (stop run loop)
     * @return cost of robot
     */
    public synchronized int kill() {
        isRunning = false;

        wakeUp();

        // zwolnij krzyżówki
        if (outcomingTile != incomingTile) {
            unoccupyCheckpoint(outcomingTile);
        }
        unoccupyCheckpoint(incomingTile);

        removeFromProperPanel();

        //willispace.WilliSpace.notifyAllRobots();

        return 0;
    }

    /**
     * sleeps robots thread
     */
    public synchronized void goSleep() {
        try {
            wait();
        } catch (InterruptedException ex) {
        }
        updateLocation();
    }

    /**
     * 
     */
    public synchronized void wakeUp() {
        notifyAll();
    }

    /**
     * 
     * @return if incoming tile matches outcoming tile or position of incoming tile matches robot position
     */
    protected boolean checkTileCovering() {
        return incomingTile == outcomingTile || (this.x == incomingTile.getXY().x && this.y == incomingTile.getXY().y);
    }

    /**
     * 
     * @return true if next checkpoint exists
     */
    protected boolean isNextCheckpoint() {
        return route.getRouteLength() > 0;
    }

    /**
     * 
     * @return next checkpoint at route
     * @throws RouteIndexOutOfRangeException if checkpoint doesn't exist
     */
    public Tile getNextCheckpoint() throws RouteIndexOutOfRangeException {
        return route.getNextCheckpoint(incomingTile);
    }

    /**
     * try to occupy next checkpoint, by taking its crossing
     * @return  next Tile
     * @throws RouteIndexOutOfRangeException    if next checkpoint doesn't exist
     * @throws CrossingOccupiedException        if crossing can't be taken  
     */
    protected Tile occupyNextCheckpoint() throws RouteIndexOutOfRangeException, CrossingOccupiedException {
        // get next checkpoint
        Tile nextTile = getNextCheckpoint();

        setNewDirections(outcomingTile, nextTile);
        
        // take next crossing
        nextTile.occupy(this);
        return nextTile;
    }

    /**
     * unocuppy crossing
     * @param tile  tile with crossing
     */
    protected void unoccupyCheckpoint(Tile tile) {
        tile.unoccupy(this);
    }

    private void directionsUpdate() {
        dx = incomingTile.getIndexes().x - outcomingTile.getIndexes().x;
        dy = incomingTile.getIndexes().y - outcomingTile.getIndexes().y;
        
        //dxCopy = dx;
        //dyCopy = dy;
    }


    private void resetDirections() {
        dx = dy = 0;
    }

    /**
     * Go from end of definded route to tile t
     * @param t                     tile to go
     * @throws BlockedTileException if tile is blocked (e.g. by rock)
     */
    public synchronized void go(Tile t) throws BlockedTileException {
        Tile startTile = incomingTile;
        try {
            startTile = route.getEnd();
        } catch (EmptyRouteException ex) {
        }
        Route newRoute = new Route(startTile, t);

        // remove first checkpoint, which is current end
        try {
            newRoute.deleteCheckpoint(0, true);
        } catch (RouteIndexOutOfRangeException ex) {
        }

        route.appends(newRoute);
        wakeUp();
    }

    /**
     * Adds new target to list
     * @param t tile with target
     * @param a action in target
     * @return  new target instance
     */
    protected Target addTarget(Tile t, Actions a) {
        Target target = new Target(t, a);
        targets.add(target);

        return target;
    }

    /**
     * gets next target
     * @return next target
     * @throws IndexOutOfBoundsException if there is no targets
     */
    protected Target getNextTarget() throws IndexOutOfBoundsException {
        return targets.get(0);
    }

    /**
     * removes next target
     * @return removed target
     */
    protected Target removeNextTarget() {
        return targets.remove(0);
    }

    /**
     * 
     * @return current directions
     */
    public Point getDirections() {
        return new Point(dxCopy, dyCopy);
    }

    /**
     * @return the isRunning
     */
    public boolean isRunning() {
        return isRunning;
    }
    
    /**
     * 
     * @return thread of robot
     */
    public Thread getThread(){
        return t;
    }

    private void setNewDirections(Tile outcomingTile, Tile nextTile) {
        dxCopy = nextTile.getIndexes().x - outcomingTile.getIndexes().x;
        dyCopy = nextTile.getIndexes().y - outcomingTile.getIndexes().y;
    }

    /**
     * defines route target and action in it
     */
    protected class Target {

        /**
         * tile
         */
        public Tile tile;
        /**
         * action
         */
        public Actions action;

        /**
         * 
         * @param tile      tile
         * @param action    action when target reached
         */
        public Target(Tile tile, Actions action) {
            this.tile = tile;
            this.action = action;
        }
    }
}
